use anyhow::bail;
use md5::{Digest, Md5};
use reqwest::StatusCode;
use serde::Deserialize;
use serde_json::json;
use std::time::Duration;
use uuid::{Builder, Uuid};
#[derive(Deserialize)]
struct Profile {
    id: String,
    #[allow(dead_code)]
    name: String,
}

pub fn get_offline_uuid_by_name(username: &str) -> String {
    let full_string = format!("OfflinePlayer:{}", username);
    let mut hasher = Md5::new();
    hasher.update(full_string);
    let hash = hasher.finalize();
    let mut bytes: [u8; 16] = hash.into();
    bytes[6] = (bytes[6] & 0x0F) | 0x30; // 版本 3
    bytes[8] = (bytes[8] & 0x3F) | 0x80; // 变体 2
    let mut builder = Builder::from_bytes(bytes);
    let builder = builder
        .set_version(uuid::Version::Md5)
        .set_variant(uuid::Variant::RFC4122);
    builder.as_uuid().to_string()
}

pub async fn get_online_uuid_by_name(username: &str) -> Result<String, Box<dyn std::error::Error>> {
    let url = format!(
        "https://api.mojang.com/users/profiles/minecraft/{}",
        username
    );
    let client = reqwest::Client::builder()
        .timeout(Duration::from_secs(10))
        .build()?;
    let response: Profile = client.get(url).send().await?.json().await?;
    let uuid = Uuid::parse_str(&response.id)?;
    Ok(uuid.to_string())
}

pub async fn get_online_uuid_by_token(token: &str) -> anyhow::Result<String> {
    let url = "https://api.minecraftservices.com/minecraft/profile";
    let client = reqwest::Client::builder()
        .timeout(Duration::from_secs(10))
        .build()?;
    let response = client
        .get(url)
        .header("Authorization", format!("Bearer {}", token))
        .send()
        .await?;
    match response.status() {
        StatusCode::NOT_FOUND => {
            bail!("No Minecraft Profile")
        }
        StatusCode::OK => {
            let response: Profile = response.json().await?;
            let uuid = Uuid::parse_str(&response.id)?;
            Ok(uuid.to_string())
        }
        _ => {
            bail!("Unknown Error")
        }
    }
}

pub async fn get_mc_token(
    TokenUhs {
        uhs,
        token: xsts_token,
    }: TokenUhs,
) -> anyhow::Result<String> {
    let url = "https://api.minecraftservices.com/authentication/login_with_xbox";
    let client = reqwest::Client::builder()
        .timeout(Duration::from_secs(10))
        .build()?;
    let data = json!({
        "identityToken": format!("XBL3.0 x={};{}",uhs,xsts_token)
    });
    let response = client
        .post(url)
        .header("Content-Type", "application/json")
        .header("Accept", "application/json")
        .json(&data)
        .send()
        .await?;
    match response.status() {
        StatusCode::OK => {
            let response: serde_json::Value = response.json().await?;
            let token = response["access_token"].as_str().unwrap().to_string();
            Ok(token)
        }
        _ => {
            bail!("XSTS Token Error")
        }
    }
}

pub async fn get_mc_xsts_token(xbl_token: &str) -> anyhow::Result<TokenUhs> {
    let url = "https://xsts.auth.xboxlive.com/xsts/authorize";
    let client = reqwest::Client::builder()
        .timeout(Duration::from_secs(10))
        .build()?;

    let data = json!({
        "Properties": {
            "SandboxId": "RETAIL",
            "UserTokens": [xbl_token]
        },
        "RelyingParty": "rp://api.minecraftservices.com/",
        "TokenType": "JWT"
    });
    let response = client
        .post(url)
        .header("Content-Type", "application/json")
        .header("Accept", "application/json")
        .json(&data)
        .send()
        .await?;
    match response.status() {
        StatusCode::OK => {
            let response: serde_json::Value = response.json().await?;
            let token = response["Token"].as_str().unwrap().to_string();
            let uhs = response["DisplayClaims"]["xui"][0]["uhs"]
                .as_str()
                .unwrap()
                .to_string();
            Ok(TokenUhs { uhs, token })
        }
        _ => {
            bail!("XBL Token Error")
        }
    }
}
pub struct TokenUhs {
    pub uhs: String,
    pub token: String,
}
pub async fn get_xbl_token(access_token: &str) -> anyhow::Result<TokenUhs> {
    let url = "https://user.auth.xboxlive.com/user/authenticate";
    let client = reqwest::Client::builder()
        .timeout(Duration::from_secs(10))
        .build()?;

    let data = json!({
        "Properties": {
            "AuthMethod": "RPS",
            "SiteName": "user.auth.xboxlive.com",
            "RpsTicket": format!("d={}",access_token)
        },
        "RelyingParty": "http://auth.xboxlive.com",
        "TokenType": "JWT"
    });
    let response = client
        .post(url)
        .header("Content-Type", "application/json")
        .header("Accept", "application/json")
        .json(&data)
        .send()
        .await?;
    match response.status() {
        StatusCode::OK => {
            let response: serde_json::Value = response.json().await?;
            let token = response["Token"].as_str().unwrap().to_string();
            let uhs = response["DisplayClaims"]["xui"][0]["uhs"]
                .as_str()
                .unwrap()
                .to_string();
            Ok(TokenUhs { uhs, token })
        }
        _ => {
            bail!("MS Token Error")
        }
    }
}
/// CLIENT ID USE PRISM LAUNCHER
const CLIENT_ID: &str = "c36a9fb6-4f2a-41ff-90bd-ae7cc92031eb";
pub async fn get_access_token(device_code: &str) -> anyhow::Result<String> {
    let url = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token";
    let client = reqwest::Client::builder()
        .timeout(Duration::from_secs(10))
        .build()?;

    let form = [
        ("client_id", CLIENT_ID),
        ("grant_type", "urn:ietf:params:oauth:grant-type:device_code"),
        ("device_code", device_code),
    ];
    let response = client
        .post(url)
        .header("Content-Type", "application/x-www-form-urlencoded")
        .form(&form)
        .send()
        .await?;
    match response.status() {
        StatusCode::OK => {
            let response: serde_json::Value = response.json().await?;
            let token = response["access_token"].as_str().unwrap().to_string();
            Ok(token)
        }
        _ => {
            bail!("Device Code Error")
        }
    }
}

pub async fn get_device_code() -> anyhow::Result<String> {
    let url = "https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode";
    let client = reqwest::Client::builder()
        .timeout(Duration::from_secs(10))
        .build()?;

    let form = [
        ("client_id", CLIENT_ID),
        ("scope", "XboxLive.signin offline_access"),
    ];
    let response = client
        .post(url)
        .header("Content-Type", "application/x-www-form-urlencoded")
        .form(&form)
        .send()
        .await?;
    match response.status() {
        StatusCode::OK => {
            let response: serde_json::Value = response.json().await?;
            let code = response["device_code"].as_str().unwrap().to_string();
            Ok(code)
        }
        _ => {
            bail!("Device Code Error")
        }
    }
}
